﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Wombat.Data
{
    public class SqlDal : IDal
    {
        private readonly ITempFileService _tempFileService;

        public SqlDal(ITempFileService tempFileService)
        {
            _tempFileService = tempFileService;
        }

        public async Task<IDbConnection> Connect(string connectionString)
        {
            Contract.Assert(!string.IsNullOrEmpty(connectionString));
            var conn = new SqlConnection(connectionString);
            await conn.OpenAsync();
            return conn;
        }

        public async Task<IEnumerable<T>> ExecuteReaderAsync<T>(IDbConnection conn, string query, Func<SqlDataReader, T> recordFactory)
        {
            var command = GetCommand(conn, query);
            var reader = await command.ExecuteReaderAsync();
            return IterateReader(reader, recordFactory);
        }

        private static SqlCommand GetCommand(IDbConnection conn, string query)
        {
            Contract.Assert(conn is SqlConnection);
            Contract.Assert(conn != null);
            Contract.Assert(conn.State == ConnectionState.Open);
            var command = new SqlCommand(query, conn as SqlConnection);
            return command;
        }

        private static IEnumerable<T> IterateReader<T>(SqlDataReader reader, Func<SqlDataReader, T> recordFactory)
        {
            while (reader.Read())
            {
                yield return recordFactory(reader);
            }
        }

        public async Task ExecuteNotQueryAsync(IDbConnection conn, string query)
        {
            var command = GetCommand(conn, query);
            await command.ExecuteNonQueryAsync();
        }

        public async Task BulkInsertAsync<T>(IDbConnection conn, string table, IEnumerable<T> data, Func<T, string> toString)
        {
            var tempFileName = _tempFileService.GetFileName();
            using (var sourceStream = new StreamWriter(File.OpenWrite(tempFileName), Encoding.Unicode))
            {
                var sb = new StringBuilder();
                foreach (var stringData in data.Select(toString))
                {
                    sb.Append(stringData);
                    await sourceStream.WriteAsync(sb.Append("\r\n").ToString());
                    sb.Clear();
                }
                sourceStream.Close();
            }
            var command = GetCommand(conn, string.Format(Queries.BulkInsertQuery, table, tempFileName));
            await command.ExecuteNonQueryAsync();
        }

        public async Task<T> ExecuteScalarAsync<T>(IDbConnection conn, string query)
        {
            var command = GetCommand(conn, query);
            var rv = await command.ExecuteScalarAsync();
            if (rv == null)
                return default(T);
            return (T)Convert.ChangeType(rv, typeof(T));
        }

        public async Task<IEnumerable<T>> ExecuteReaderReadAllAsync<T>(IDbConnection conn, string query, Func<SqlDataReader, T> rowConverter)
        {
            var command = GetCommand(conn, query);
            var reader = await command.ExecuteReaderAsync();
            var rv = new List<T>();
            while (await reader.ReadAsync())
            {
                rv.Add(rowConverter(reader));
            }
            return rv;
        }
    }
}